#include "std/vga.h"
#include "arch/inout.h"
#include "kernel/spinlock.h"

#define VGA_BUF			(0xC00B8000)
#define VGA_WIDTH		(80)
#define VGA_HEIGHT		(25)

// static varables
static uint ln  = 0;
static uint col = 0;
static uint color = 7;
DECLARE_PRIVATE_SPINLOCK(vga_spinlock);

static void enable_cursor(void)
{
	outb_p(0x0A, 0x3D4);
	outb_p((inb(0x3D5) & 0xC0), 0x3D5);

	outb_p(0x0B, 0x3D4);
	outb_p((inb(0x3E0) & 0xE0) | 15, 0x3D5);
}

static void disable_cursor(void)
{
	outb_p(0x0A, 0x3D4);
	outb_p(0x20, 0x3D5);
}

static void update_cursor(void)
{
	uint pos;
	pos = col + 80 * ln;
	outb_p(14, 0x3d4);
	outb_p((unsigned char)(0xff & (pos >> 8)), 0x3d5);
	outb_p(15, 0x3d4);
	outb_p((unsigned char)(0xff & pos), 0x3d5);
}

// clear the vga screen
void vga_clear(void)
{
	spin_lock(&vga_spinlock);
	ln = col = 0;
	enable_cursor();
	update_cursor();
	memset((void*)VGA_BUF, 0,
		VGA_WIDTH * VGA_HEIGHT * 2);
	spin_unlock(&vga_spinlock);
}

// scroll up several lines
void vga_scrollup(uint lncnt)
{
	const uint vga_total_char =
		VGA_WIDTH * VGA_HEIGHT * 2;
	int move_chars;

	if (lncnt > 25)
	{
		vga_clear();
		return;
	}

	move_chars = lncnt * VGA_WIDTH * 2;

	spin_lock(&vga_spinlock);
	memmove((void *)VGA_BUF,
		(void *)(VGA_BUF + move_chars),
		vga_total_char - move_chars);
	memset((void *)(VGA_BUF + vga_total_char - move_chars),
		0, move_chars);
	spin_unlock(&vga_spinlock);
}

static char do_vga_putchar(char c)
{
	uint address = (uint)VGA_BUF;

	if (c == '\n')
	{
		col = 0;
		if (ln == VGA_HEIGHT - 1)
			vga_scrollup(1);
		else ln++;
		return c;
	}
	else if (c == '\r') {
		col = 0;
		return c;
	}
	else if (c == '\t') {
		col = (char)(((col / 8) + 1) * 8 - 1);
		return c;
	}
	else if (c == '\7')
	{
		return c;
	}
	else
	{
		address += ln * VGA_WIDTH * 2 + col * 2;
		*((unsigned short *)address) = (unsigned short)((color << 8) | c);
	}
	if (col == VGA_WIDTH - 1)
	{
		col = 0;
		if (ln == VGA_HEIGHT - 1)
			vga_scrollup(1);
		else ln++;
	}
	else col++;
	return c;
}

char vga_putchar(char c)
{
	char ret;
	spin_lock(&vga_spinlock);
	ret = do_vga_putchar(c);
	update_cursor();
	spin_unlock(&vga_spinlock);
	return ret;
}

void vga_puts(const char *str)
{
	if (NULL == str)
		return;

	// write out the string
	spin_lock(&vga_spinlock);
	while (*str)
		do_vga_putchar(*str++);

	// Set the cursor
	update_cursor();

	spin_unlock(&vga_spinlock);
}

void vga_setcolor(uint _color)
{
	spin_lock(&vga_spinlock);
	color = _color;
	spin_unlock(&vga_spinlock);
}

int vga_getcolor(void)
{
	return color;
}

void vga_setxy(uint x, uint y)
{
	spin_lock(&vga_spinlock);
	ln =  y;
	col = x;
	if (ln >= VGA_HEIGHT) ln = VGA_HEIGHT - 1;
	if (col >= VGA_WIDTH) col = VGA_WIDTH - 1;
	update_cursor();
	spin_unlock(&vga_spinlock);
}

void vga_getxy(uint *x, uint *y)
{
	if (x) *x = col;
	if (y) *y = ln;
}

void vga_clrscr(void)
{
	int i;
	unsigned short *buf = (unsigned short*)VGA_BUF;

	spin_lock(&vga_spinlock);
	ln = 0;
	col = 0;
	for (i = 0; i < VGA_WIDTH * VGA_HEIGHT; i++)
		*buf++ = (unsigned short)((color << 8) | ' ');
	enable_cursor();
	update_cursor();
	spin_unlock(&vga_spinlock);
}

/* EOF */
